﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright company="Microsoft Corp." file="InitializeDatabaseOperation.cs">
//   All rights reserved. See License.txt in the project root for license information.
// </copyright>
// 
// --------------------------------------------------------------------------------------------------------------------
namespace CloudBlitz.DeploymentManager.DeploymentOperations
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Text;

    using CloudBlitz.DeploymentManager.DeploymentWorkflow;

    public class InitializeDatabaseOperation : DeploymentOperationBase
    {
        private const string SchedulerUtilsDirectory = "SchedulerUtils";

        private readonly bool forceDatabaseInitialization;

        private readonly int initializeDbTimeoutInMilliseconds;

        public InitializeDatabaseOperation(bool forceDatabaseInitialization, int initializeDbTimeoutInMilliseconds) : base(5)
        {
            this.forceDatabaseInitialization = forceDatabaseInitialization;
            this.initializeDbTimeoutInMilliseconds = initializeDbTimeoutInMilliseconds;
        }

        protected override OperationResult ExecuteOverride(DeploymentContext context)
        {
            this.RaiseProgressChangedEvent(0, "Running database initialize util");

            var workingDirectory = Path.Combine(Environment.CurrentDirectory, SchedulerUtilsDirectory);

            // OutputPair is a pair of (log information, exitCode)
            var outputPair = new KeyValuePair<string, int>();

            // Set timeout = 1000000 miliseconds for initializeDB.exe to run
            // executing "InitializeDB /c[onfig] <cscfgPath>
            if (this.forceDatabaseInitialization)
            {
                outputPair = ExecuteCommand(
                                    workingDirectory, 
                                    "InitializeDB.exe",
                                    " /c \"" + context.ClusterDetails.ServiceConfigurationFilePath + "\" /v /f", 
                                    this.initializeDbTimeoutInMilliseconds);
            }
            else
            {
                outputPair = ExecuteCommand(
                                    workingDirectory, 
                                    "InitializeDB.exe",
                                    " /c \"" + context.ClusterDetails.ServiceConfigurationFilePath + "\" /v", 
                                    this.initializeDbTimeoutInMilliseconds);
            }

            // Verify whether initiazleDB succeeded or not by checking the exitCode
            if (outputPair.Value == 0)
            {
                this.RaiseProgressChangedEvent(100, string.Format("SQL database \"{0}\" initialized", context.SqlServerDetails.DatabaseName));
                return OperationResult.Success;
            }

            return new OperationResult(DeploymentOperationStatus.Failed, outputPair.Key.ToString() + Environment.NewLine + outputPair.Value.ToString());
        }

        /// <summary>
        /// Used for executing executable files in another process
        /// </summary>
        private static KeyValuePair<string, int> ExecuteCommand(
                                                    string workingDirectory, 
                                                    string command, 
                                                    string arguments, 
                                                    int timeoutInMiliSeconds)
        {
            if (!Directory.Exists(workingDirectory))
            {
                throw new DirectoryNotFoundException(workingDirectory);
            }

            using (var process = new Process())
            {
                process.StartInfo.FileName = Path.Combine(workingDirectory, command);
                process.StartInfo.Arguments = arguments;
                //process.StartInfo.WorkingDirectory = workingDirectory;
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardInput = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.StartInfo.CreateNoWindow = true;

                var outputMessageStringBuilder = new StringBuilder();
                process.OutputDataReceived += (sendingProcess, outLine) =>
                {
                    if (!string.IsNullOrEmpty(outLine.Data))
                    {
                        // Add the text to the collected output.
                        outputMessageStringBuilder.Append(Environment.NewLine + "  " + outLine.Data);
                    }
                };

                var errorMessageString = new StringBuilder();
                process.ErrorDataReceived += (sendingProcess, outLine) =>
                {
                    if (!string.IsNullOrEmpty(outLine.Data))
                    {
                        // Add the text to the collected output.
                        errorMessageString.Append(Environment.NewLine + "  " + outLine.Data);
                    }
                };

                process.Start();

                process.BeginErrorReadLine();
                process.BeginOutputReadLine();

                if (!process.WaitForExit(timeoutInMiliSeconds))
                {
                    throw new InvalidProgramException(
                        string.Format("{0} did not complete in {1} milliseconds", command, timeoutInMiliSeconds));
                }

                var exitCode = process.ExitCode;
                process.Close();

                var strError = errorMessageString.ToString();
                var strOutput = outputMessageStringBuilder.ToString();
                if (string.IsNullOrEmpty(strError))
                {
                    return new KeyValuePair<string, int>("Stdout: " + strOutput, exitCode);
                }

                return new KeyValuePair<string, int>(
                    "Stdout: " + strOutput + Environment.NewLine + "Stderr:" + strError, exitCode);
            }
        }
    }
}